#include "listview.h"

#define DEF_HEIGHT_PER_ROW 12      /* in pixel size */
#define DEF_ITEMS_INTERVAL 1       /* in pixel size */

extern PAGE *current_page;

void __RowOnPress(void *arg);
void __PrevOnPress(void *arg);
void __NextOnPress(void *arg);

ListView *ListView_Create(int x, int y, int width, int height, int items)
{
    ListView *lv;
    
    lv = (ListView *)malloc(sizeof(ListView));
    if(!lv)
        return NULL;
    
    lv->x = x;
    lv->y = y;
    lv->width = width;
    lv->height = height;
    lv->items = items;
    lv->properties = LV_PROPS_ROW_UNISEL;
    lv->rows_on_view = 0;
    lv->rows_current = 0;
    lv->rows_total = 0;
    lv->first_row_on_list = 0;
    lv->row_id_count = 0;
    lv->bg_color = pCOLOR_GRAY;
    lv->unsel_color = pCOLOR_WHITE;
    lv->sel_color = pCOLOR_OCEAN;
    lv->onRowPress = NULL;
    lv->row_selected = NULL;
    
    lv->title = (LV_ROW *)malloc(sizeof(LV_ROW) * items);
    if(!lv->title) {
        free(lv);
        return NULL;
    }
    INIT_LIST_HEAD(&lv->row_list);
    
    lv->background = CreateGObj(x, y, width, height, lv->bg_color);
    if(!lv->background)
        WARNING("ListView: Failed to create background object.\n");
    
    lv->prev = CreateGObj(x + width + DEF_ITEMS_INTERVAL, y, 25, 30, lv->bg_color);
    lv->next = CreateGObj(x + width + DEF_ITEMS_INTERVAL, y + 30 + DEF_ITEMS_INTERVAL, 25, 30, lv->bg_color);
    if(!lv->prev || !lv->next)
        WARNING("ListView: Failed to create list select button.\n");
    else {
        lv->prev->setText(lv->prev, L"<", 24, pCOLOR_BLACK, 
                          FONT_START_ADDR, FONT_FILE_SIZE, ALIGN_MIDDLE | ALIGN_CENTER, NULL);
        lv->next->setText(lv->next, L">", 24, pCOLOR_BLACK, 
                          FONT_START_ADDR, FONT_FILE_SIZE, ALIGN_MIDDLE | ALIGN_CENTER, NULL);
    }
    lv->prev->event_trigger_type = EVENT_TRIG_SINGLE;
    lv->next->event_trigger_type = EVENT_TRIG_SINGLE;
    lv->prev->onPress = __PrevOnPress;
    lv->next->onPress = __NextOnPress;
    lv->prev->private_data = (void *)lv;
    lv->next->private_data = (void *)lv;
    
    return lv;
}

int __CreateListGobjs(ListView *lv)
{
    int rows, items;
    int i, j;
    int x, y;
    int row_height;
    LVPrivData *priv;
    
    if(!lv)
        return -1;
    
    if(lv->title && lv->title[0])
        lv->rows_on_view = (lv->height/(lv->title[0]->height + DEF_ITEMS_INTERVAL)) - 1;
    else
        return -2; /* No title row */
    
    lv->rows = (LV_ROW **)malloc(sizeof(LV_ROW *) * lv->rows_on_view);
    if(!lv->rows)
        return -3;
    
    row_height = lv->title[0]->height + DEF_ITEMS_INTERVAL;
    y = lv->title[0]->y + lv->title[0]->height + DEF_ITEMS_INTERVAL;
    for(rows = 0; rows < lv->rows_on_view; y += row_height, rows++) {
        lv->rows[rows] = (LV_ROW *)malloc(sizeof(LV_ROW) * lv->items);
        if(!lv->rows[rows]) {
            for(i = 0; i < rows; i++)
                free(lv->rows[i]);
            free(lv->rows);
            return -4;
        }
        
        priv = (LVPrivData*)malloc(sizeof(LVPrivData));
        priv->lv = lv;
        priv->row_data = NULL;
        
        for(items = 0, x = lv->x+DEF_ITEMS_INTERVAL; items < lv->items; x+=(lv->title[items]->width+DEF_ITEMS_INTERVAL), items++) {
            lv->rows[rows][items] = CreateGObj(x, y, lv->title[items]->width, lv->title[0]->height, lv->unsel_color);
            if(!lv->rows[rows][items]) {
                for(i = 0; i < rows; i++) {
                    for(j = 0; j < items; j++)
                        DestroyGObj(lv->rows[i][j]);
                    free(lv->rows[i]);
                }
                free(lv->rows);
                return -5;
            }
            lv->rows[rows][items]->event_trigger_type = EVENT_TRIG_SINGLE;
            
            /* Install press event handler and private data */
            lv->rows[rows][items]->onPress = __RowOnPress;
            lv->rows[rows][items]->private_data = (void *)priv;
        }
    }
    
    return 0;
}

int ListView_SetRowTitles(ListView *lv, unicode_t *title_text[], int title_width[], int size)
{
    int i, j, x;
    
    if(!lv)
        return -1;
    
    if(lv->items <= 0 || !lv->title)
        return -1;
        
    if(!title_text || !title_width || size <= 0)
        return -2;
        
    for(i = 0, x = lv->x+DEF_ITEMS_INTERVAL; i < lv->items; x+=(title_width[i]+DEF_ITEMS_INTERVAL), i++) {
        lv->title[i] = CreateGObj(x, lv->y+DEF_ITEMS_INTERVAL, title_width[i], size, pCOLOR_GRAY);
        if(!lv->title[i]) {
            for(j = 0; j < i; j++)
                DestroyGObj(lv->title[j]);
            return -3;
        }
        lv->title[i]->setText(lv->title[i], title_text[i], size, pCOLOR_BLACK, 
                              FONT_START_ADDR, FONT_FILE_SIZE, ALIGN_MIDDLE | ALIGN_CENTER, NULL);
        /* Update GObj height with actual text pixel size */
        lv->title[i]->height = lv->title[i]->text->txt_height;
    }
    
    __CreateListGobjs(lv);
    
    return 0;
}

LV_ROW_ID ListView_AddRows(ListView *lv, unicode_t *row_text[])
{
    int i;
    RowListNode *row_data;
    
    if(!lv)
        return -1;
    
    if(lv->items <= 0)
        return -2;
        
    if(!lv->title)
        return -3;
        
    if(!row_text)
        return -4;
    
    row_data = (RowListNode *)malloc(sizeof(RowListNode));
    if(!row_data)
        return -5;
        
    INIT_LIST_HEAD(&row_data->list);
    
    row_data->row_text = (unicode_t **)malloc(sizeof(unicode_t *) * lv->items);
    if(!row_data->row_text) {
        free(row_data);
        return -6;
    }
    
    for(i = 0; i < lv->items; i++) {
        row_data->row_text[i] = uni_strdup(row_text[i]);
        if(!row_data->row_text[i])
            WARNING("Duplicating string error on item %d\n", i);
    }
    
    /* Add this row to the list */
    list_add_tail(&row_data->list, &lv->row_list);
    
    row_data->selected = false;
    row_data->row_id = ++lv->row_id_count;
    row_data->row_id_on_view = -1;
    lv->rows_total++;
    
    if((lv->rows_current + 1) > lv->rows_on_view) {
        return row_data->row_id;
    }
    
    for(i = 0; i < lv->items; i++) {
        lv->rows[lv->rows_current][i]->setText(lv->rows[lv->rows_current][i], 
                                               row_data->row_text[i], 
                                               lv->title[i]->text->size, 
                                               pCOLOR_BLACK, 
                                               FONT_START_ADDR, 
                                               FONT_FILE_SIZE, 
                                               ALIGN_MIDDLE | ALIGN_CENTER, 
                                               NULL);
    }
    
    row_data->row_id_on_view = lv->rows_current;
    
    /* Save row_data pointer for GObjs of this row (they share the same LVPrivData) */
    ((LVPrivData *)lv->rows[lv->rows_current][0]->private_data)->row_data = row_data;

    lv->rows_current++;
    
    if(lv->first_row_on_list == 0)
        lv->first_row_on_list = row_data->row_id;
    
    return row_data->row_id;
}

int ListView_RemoveRow(ListView *lv, LV_ROW_ID row_id)
{
    RowListNode *row, *tmp;
    struct list_head *h;
    int i;
    
    if(!lv)
        return -1;
    
    list_for_each(h, &lv->row_list) {
        row = container_of(h, RowListNode, list);
        if(!row)
            return -2;
        
        if(row->row_id == row_id) {
            if(row_id == lv->first_row_on_list) {
                if(h->next != &lv->row_list) {
                    tmp = container_of(h->next, RowListNode, list);
                    lv->first_row_on_list = tmp->row_id;
                }
                else if(h->prev != &lv->row_list) {
                    tmp = container_of(h->prev, RowListNode, list);
                    lv->first_row_on_list = tmp->row_id;
                }
                else
                    lv->first_row_on_list = 0;
            }
            if(lv->row_selected == row) {
                __RowSetSelect(lv, row, false);
                lv->row_selected = NULL;
            }
            //((LVPrivData *)lv->rows[row->row_id_on_view][0]->private_data)->row_data = NULL;
            list_del(h);
            for(i = 0; i < lv->items; i++)
                free(row->row_text[i]);
            free(row);
            lv->rows_total--;
            if(lv->rows_total < lv->rows_on_view)
                lv->rows_current--;
            return 1;
        }
    }
    
    return 0;
}

int ListView_RemoveSelectRow(ListView *lv)
{
    RowListNode *row;
    struct list_head *h;
    int i;
    
    if(!lv)
        return -1;
        
    list_for_each(h, &lv->row_list) {
        row = container_of(h, RowListNode, list);
        if(!row)
            continue;
        
        if(row->selected) {
			__RowSetSelect(lv, row, false);
            list_del(h);
            for(i = 0; i < lv->items; i++)
                free(row->row_text[i]);
            free(row);
            lv->rows_total--;
            if(lv->rows_total < lv->rows_on_view)
                lv->rows_current--;
        }
    }
    
    return 0;
}

int ListView_AddtoPage(PAGE *p, ListView *lv)
{
    int i, rows;
    
    if(!lv || !p)
        return -1;
    
    if(!lv->title)
        return -2;
        
    if(lv->items <= 0)
        return -3;
        
    lv->page = p;

    if(lv->background)
        PGAddGObj(p, lv->background);
        
    if(lv->prev && lv->next) {
        PGAddGObj(p, lv->prev);
        PGAddGObj(p, lv->next);
    }
    
    for(i = 0; i < lv->items; i++) {
        if(lv->title[i])
            PGAddGObj(p, lv->title[i]);
    }
    
    for(rows = 0; rows < lv->rows_on_view; rows++) {
        for(i = 0; i < lv->items; i++) {
            if(lv->rows[rows][i])
                PGAddGObj(p, lv->rows[rows][i]);
        }
    }
    
    return 0;
}

int ListView_Show(ListView *lv)
{
    //int i;
    //RowListNode *tmp;
    //struct list_head *h;
    //BOOLEAN show = false;
    //int show_cnt = 0;
    
    if(!lv)
        return -1;
    
    if(!lv->title)
        return -2;
        
    if(lv->items <= 0)
        return -3;
        
    if(lv->page != current_page)
        return -4;
    
    if(lv->background)
        lv->background->draw(lv->background);
/* maybe we don't need the following code if using redraw 
    for(i = 0; i < lv->items; i++) {
        if(lv->title[i])
            lv->title[i]->redraw(lv->title[i]);
    }
    
    list_for_each(h, &lv->row_list) {
        tmp = container_of(h, RowListNode, list);
        if(!tmp)
            continue;
        
        if(tmp->row_id == lv->first_row_on_list)
            show = true;

        if(!show)
            continue;
            
        for(i = 0; i < lv->items; i++) {
            if(lv->rows[show_cnt][i])
                lv->rows[show_cnt][i]->redraw(lv->rows[show_cnt][i]);
        }
        
        tmp->row_id_on_view = show_cnt;
        
        if(++show_cnt > lv->rows_on_view)
            break;
    }
*/
    return 0;
}

int ListView_UpdateAll(ListView *lv)
{
    int i;
    RowListNode *tmp;
    struct list_head *h;
    BOOLEAN show = false;
    int show_cnt = 0;
    
    if(!lv)
        return -1;
        
    if(lv->items <= 0)
        return -2;
        
    if(lv->page != current_page)
        return -3;
        
    list_for_each(h, &lv->row_list) {
        tmp = container_of(h, RowListNode, list);
        if(!tmp)
            return -4;
        
        if(tmp->row_id == lv->first_row_on_list)
            show = true;

        if(!show)
            continue;
                    
        for(i = 0; i < lv->items; i++) {
            if(lv->rows[show_cnt][i]) {
                if(tmp->selected)
                    lv->rows[show_cnt][i]->text->bg_color = lv->sel_color;
                else
                    lv->rows[show_cnt][i]->text->bg_color = lv->unsel_color;
                lv->rows[show_cnt][i]->setText(lv->rows[show_cnt][i], tmp->row_text[i], lv->rows[show_cnt][i]->text->size, 
                                               pCOLOR_BLACK, FONT_START_ADDR, FONT_FILE_SIZE, lv->rows[show_cnt][i]->text_align, NULL);
                lv->rows[show_cnt][i]->draw(lv->rows[show_cnt][i]);
            }
        }
        ((LVPrivData *)(lv->rows[show_cnt][0]->private_data))->row_data = tmp;
        tmp->row_id_on_view = show_cnt++;
        if(show_cnt >= lv->rows_on_view)
            break;
    }
    
    /* Clear text of the rest GObjs */
    for(; show_cnt < lv->rows_on_view; show_cnt++) {
        for(i = 0; i < lv->items; i++) {
            if(lv->rows[show_cnt][i]) {
                lv->rows[show_cnt][i]->color = lv->unsel_color;
                lv->rows[show_cnt][i]->updateText(lv->rows[show_cnt][i], NULL);
                lv->rows[show_cnt][i]->draw(lv->rows[show_cnt][i]);
            }
        }
        ((LVPrivData *)(lv->rows[show_cnt][0]->private_data))->row_data = NULL;
    }
    
    return 0;
}

int ListView_UpdateRow(ListView *lv, LV_ROW_ID row_id, unicode_t *row_text[])
{
    int i;
    RowListNode *tmp;
    struct list_head *h;
    int show_cnt = 0;
    BOOLEAN onlist = false;
    
    if(!lv)
        return -1;
        
    if(lv->items <= 0)
        return -2;
        
    if(!row_text)
        return -3;
        
    list_for_each(h, &lv->row_list) {
        tmp = container_of(h, RowListNode, list);
        if(!tmp)
            break;
        
        if(tmp->row_id == lv->first_row_on_list)
            onlist = true;
            
        if(tmp->row_id == row_id) {
            for(i = 0; i < lv->items; i++) {
                if(tmp->row_text[i])
                    free(tmp->row_text[i]);
                tmp->row_text[i]= uni_strdup(row_text[i]);
                if(onlist && lv->rows[show_cnt][i]) {
                    lv->rows[show_cnt][i]->updateText(lv->rows[show_cnt][i], tmp->row_text[i]);
                    if(lv->page == current_page)
                        lv->rows[show_cnt][i]->draw(lv->rows[show_cnt][i]);
                }
            }
            return 1;
        }
        
        if(onlist)
            ++show_cnt;
            
        if(show_cnt >= lv->rows_on_view)
            onlist = false;
    }
    
    return 0;
}

inline void ListView_SetStartRow(ListView *lv, LV_ROW_ID row_id)
{
    lv->first_row_on_list = row_id;
}

void __RowSetSelect(ListView *lv, RowListNode *row_data, BOOLEAN selected)
{
    GOBJ *g;
    int i;
	
    if(!row_data) {
	    WARNING("[%s] row_data is NULL\n", __func__);
	    return;
    }
    
    row_data->selected = selected;
    
    for(i = 0; i < lv->items; i++) {
        g = lv->rows[row_data->row_id_on_view][i];
        if(selected)
            g->color = lv->sel_color;
        else
            g->color = lv->unsel_color;
        
        if(g->text && g->text->txt_count > 0 && g->text->string) {
            g->setText(g, g->text->string, g->text->size, 
                       pCOLOR_BLACK, FONT_START_ADDR, FONT_FILE_SIZE, g->text_align, NULL);
        }
        g->draw(g);
    }
}

void __RowOnPress(void *arg)
{
    GOBJ *gobj = (GOBJ *)arg;
    ListView *lv;
    RowListNode *row_data;
    
    lv = ((LVPrivData *)gobj->private_data)->lv;
    row_data = ((LVPrivData *)gobj->private_data)->row_data;
    
    if(!lv || !row_data) {
        return;
    }
    
    //if(row_data->selected) {
    //    printf("[%s]Select: NONE\n", __func__);
    //    return;
    //}
    
    //printf("[%s]Select: %d\n", __func__, row_data->row_id);
    
    if(!(lv->properties & LV_PROPS_ROW_MULTISEL)) {
        /* LV_PROPS_ROW_UNISEL */
        if((RowListNode *)lv->row_selected != row_data) {
            if(lv->row_selected) {
                __RowSetSelect(lv, (RowListNode *)lv->row_selected, false);
            }
        }
    }
    else {
        /* LV_PROPS_ROW_MULTISEL */
        row_data->selected = ~row_data->selected;
    }

    lv->row_selected = (void *)row_data;
    __RowSetSelect(lv, row_data, true);

    if(lv->onRowPress)
        lv->onRowPress(gobj->private_data);
}

void __PrevOnPress(void *arg)
{
    GOBJ *gobj = (GOBJ *)arg;
    ListView *lv;
    RowListNode *row_data;
    struct list_head *node;
    int i;
    
    lv = (ListView *)gobj->private_data;
    if(!lv)
        return;
    
    list_for_each(node, &lv->row_list) {
        row_data = container_of(node, RowListNode, list);
        if(row_data->row_id == lv->first_row_on_list) {
            for(i = 0; i < lv->rows_on_view; i++) {
                if(node->prev == &lv->row_list)
                    break;
                node = node->prev;
            }
            row_data = container_of(node, RowListNode, list);
            ListView_SetStartRow(lv, row_data->row_id);
            ListView_UpdateAll(lv);
            return;
        }
    }
}

void __NextOnPress(void *arg)
{
    GOBJ *gobj = (GOBJ *)arg;
    ListView *lv;
    RowListNode *row_data;
    struct list_head *node;
    int i;
    
    lv = (ListView *)gobj->private_data;
    if(!lv)
        return;
    
    list_for_each(node, &lv->row_list) {
        row_data = container_of(node, RowListNode, list);
        if(row_data->row_id == lv->first_row_on_list) {
            for(i = 0; i < lv->rows_on_view; i++) {
                if(node->next == &lv->row_list)
                    return;
                node = node->next;
            }
            row_data = container_of(node, RowListNode, list);
            ListView_SetStartRow(lv, row_data->row_id);
            ListView_UpdateAll(lv);
            return;
        }
    }
}
